#include "LvRenderer.h"

#include <stdexcept>
#include <array>

LvRenderer::LvRenderer(LvWindow& window, LvDevice& device)
	:lvWindow{window}, lvDevice{device}
{
	recreateSwapChain();
	createCommandBuffers();
}

LvRenderer::~LvRenderer()
{
	freeCommandBuffers();
}

VkCommandBuffer LvRenderer::beginFrame()
{
	auto result = lvSwapChain->acquireNextImage(&currentImageIndex);

	if (result == VK_ERROR_OUT_OF_DATE_KHR)
	{
		recreateSwapChain();
		return VK_NULL_HANDLE;
	}
	else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
	{
		throw std::runtime_error("failed to acquire swap chain image");
	}

	isFrameStarted = true;

	auto commandBuffer = getCurrentCommandBuffer();
	VkCommandBufferBeginInfo beginInfo{};
	beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;

	if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS)
	{
		throw std::runtime_error("failed to begin recording command buffer!");
	}

	return commandBuffer;
}

void LvRenderer::endFrame()
{
	assert(isFrameStarted && "Can't call endFrame while frame is not in progress!");
	auto commandBuffer = getCurrentCommandBuffer();

	if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS)
	{
		throw std::runtime_error("faild to record command buffer");
	}


	auto result = lvSwapChain->submitCommandBuffers(&commandBuffer, &currentImageIndex);

	if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || lvWindow.wasWindowResizedFlag())
	{
		lvWindow.resetWindowResizedFlag();
		recreateSwapChain();
	}
	else if (result != VK_SUCCESS)
	{
		throw std::runtime_error("failed to present swap chain image!");
	}


	isFrameStarted = false;
	currentFrameIndex = (currentFrameIndex + 1) % LvSwapChain::MAX_FRAMES_IN_FLIGHT;
}

void LvRenderer::beginSwapChainRenderPass(VkCommandBuffer commandBuffer)
{
	assert(isFrameStarted && "Can't call beginSwapChainRenderPass while frame is not in progress!");
	assert(commandBuffer == getCurrentCommandBuffer() && "Can't begin render pass on command buffer from a different frame");

	VkRenderPassBeginInfo renderPassInfo{};
	renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
	renderPassInfo.renderPass = lvSwapChain->getRenderPass();
	renderPassInfo.framebuffer = lvSwapChain->getFrameBuffer(currentImageIndex);
	renderPassInfo.renderArea.offset = { 0, 0 };
	renderPassInfo.renderArea.extent = lvSwapChain->getSwapChainExtent();

	std::array<VkClearValue, 2> clearValues{};
	clearValues[0].color = { 0.0f, 0.0f, 0.0f, 1.0f };
	clearValues[1].depthStencil = { 1.0f, 0 };
	renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
	renderPassInfo.pClearValues = clearValues.data();

	vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

	VkViewport viewport{};
	viewport.x = 0.0f;
	viewport.y = 0.0f;
	viewport.width = static_cast<float>(lvSwapChain->getSwapChainExtent().width);
	viewport.height = static_cast<float>(lvSwapChain->getSwapChainExtent().height);
	viewport.minDepth = 0.0f;
	viewport.maxDepth = 1.0f;
	VkRect2D scissor{ {0, 0}, lvSwapChain->getSwapChainExtent() };
	vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
	vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
}

void LvRenderer::endSwapChainRenderPass(VkCommandBuffer commandBuffer)
{
	assert(isFrameStarted && "Can't call endSwapChainRenderPass while frame is not in progress!");
	assert(commandBuffer == getCurrentCommandBuffer() && "Can't end render pass on command buffer from a different frame");

	vkCmdEndRenderPass(commandBuffer);
}

void LvRenderer::createCommandBuffers()
{
	commandBuffers.resize(LvSwapChain::MAX_FRAMES_IN_FLIGHT);

	VkCommandBufferAllocateInfo alloInfo{};
	alloInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
	alloInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
	alloInfo.commandPool = lvDevice.getCommandPool();
	alloInfo.commandBufferCount = static_cast<uint32_t>(commandBuffers.size());
	if (vkAllocateCommandBuffers(lvDevice.device(), &alloInfo, commandBuffers.data()) != VK_SUCCESS)
	{
		throw std::runtime_error("failed to allocate command buffers.");
	}
}

void LvRenderer::freeCommandBuffers()
{
	vkFreeCommandBuffers(lvDevice.device(), lvDevice.getCommandPool(),
		static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
	commandBuffers.clear();
}

void LvRenderer::recreateSwapChain()
{
	auto extent = lvWindow.getExtent();
	while (extent.width == 0 || extent.height == 0)
	{
		extent = lvWindow.getExtent();
		glfwWaitEvents();
	}
	vkDeviceWaitIdle(lvDevice.device());

	if (lvSwapChain == nullptr)
	{
		lvSwapChain = std::make_unique<LvSwapChain>(lvDevice, extent);
	}
	else
	{
		std::shared_ptr<LvSwapChain> oldSwapchain = std::move(lvSwapChain);
		lvSwapChain = std::make_unique<LvSwapChain>(lvDevice, extent, oldSwapchain);

		if (!oldSwapchain -> compareSwapFormats(*lvSwapChain.get()))
		{
			throw std::runtime_error("Swap chain image (or depth) fromat has changed!");
		}
	}
}
